<template>
  <div class="data-processing-dag-node">
    <div
      class="main-area"
      :onMouseEnter="onMainMouseEnter"
      :onMouseLeave="onMainMouseLeave"
    >
      <div class="main-info">
        <!-- 节点类型icon -->
        <i
          class="node-logo"
          :style="`background-image: url(${NODE_TYPE_LOGO[type]})`"
          @click="onClickNode"
        ></i>
        <n-tooltip trigger="hover">
          <template #trigger>
            <div class="ellipsis-row node-name">{{ name }}</div>
          </template>
          {{ name }}
        </n-tooltip>
      </div>

      <!-- 节点状态信息 -->
      <div class="status-action">
        <i class="status-icon status-icon-success"></i>
      </div>
    </div>

    <!-- 添加下游节点  -->
    <div class="plus-dag" v-if="type !== NodeType.END">
      <n-dropdown
        trigger="click"
        :options="PROCESSING_TYPE_LIST"
        @select="(value) => clickPlusDragMenu(value, type)"
      >
        <i :class="`plus-action ${plusActionSelected ? 'plus-action-selected' : ''}`"></i>
      </n-dropdown>
    </div>

    <n-drawer v-model:show="drawerShow" :width="520">
      <n-drawer-content :title="drawerTitle" :native-scrollbar="false">
        <n-form
          :model="setNodeData"
          :rules="rules"
          ref="formRef"
          label-placement="top"
          :label-width="120"
          class="py-4"
        >
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="节点名称"
            path="name"
          >
            <n-input
              maxlength="12"
              show-count
              placeholder="请输入节点名称"
              v-model:value="setNodeData.name"
            />
          </n-form-item>
          <div v-if="setNodeData.type === NodeType.CONDITION">
            <n-divider dashed>条件表达式</n-divider>
            <n-card>
              <n-form-item label="条件字段（表单设计的字段）" path="condition.field">
                <n-input
                  maxlength="120"
                  show-count
                  placeholder="请输入条件字段"
                  v-model:value="setNodeData.condition.field"
                />
              </n-form-item>
              <n-form-item label="字段描述" path="condition.desc">
                <n-input
                  maxlength="120"
                  show-count
                  placeholder="请输入字段描述"
                  v-model:value="setNodeData.condition.desc"
                />
              </n-form-item>
              <n-form-item label="条件" path="condition.term">
                <n-select
                  v-model:value="setNodeData.condition.term"
                  placeholder="请选择条件"
                  :options="conditionOptions"
                />
              </n-form-item>
              <n-form-item label="值" path="condition.conditionValue">
                <n-input
                  maxlength="120"
                  show-count
                  placeholder="请输入值"
                  v-model:value="setNodeData.condition.conditionValue"
                />
              </n-form-item>
            </n-card>
          </div>
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="审批人"
            path="headUser"
          >
            <n-select
              v-model:value="setNodeData.headUser"
              multiple
              placeholder="请选择审批人"
              :options="headUsers"
            >
              <template #action>
                <div>
                  <div style="margin-bottom: 10px;">
                    <n-input placeholder="输入用户姓名回车搜索" clearable @change="handleChange">
                      <template #prefix>
                        <n-icon><SearchOutlined/></n-icon>
                      </template>
                      <template #suffix>
                        <n-icon><EnterOutlined/></n-icon>
                      </template>
                    </n-input>
                  </div>
                  <BasicTable
                    :columns="userColumns"
                    :request="loadDataTable"
                    :row-key="(row) => row.id"
                    ref="actionRef"
                    id="moss-basicTable"
                    :bordered="false"
                    :actionColumn="actionColumn"
                    :max-height="200"
                    :showTopOps="false"
                  >
                    <template #action>
                      <TableAction />
                    </template>
                  </BasicTable>
                </div>
              </template>
            </n-select>
          </n-form-item>
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="会签/或签"
            path="sign"
          >
            <n-radio-group v-model:value="setNodeData.sign" name="sign">
              <n-radio-button value="OR"> 或签（审批人其中一个同意） </n-radio-button>
              <n-radio-button value="ALL"> 会签（审批人全部同意） </n-radio-button>
            </n-radio-group>
          </n-form-item>
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="抄送给"
            path="ccToUser"
          >
          <n-select
              v-model:value="setNodeData.ccToUser"
              multiple
              placeholder="请选择抄送人"
              :options="ccToUsers"
            >
              <template #action>
                <div>
                  <div style="margin-bottom: 10px;">
                    <n-input placeholder="输入用户姓名回车搜索" clearable @change="handleChange">
                      <template #prefix>
                        <n-icon><SearchOutlined/></n-icon>
                      </template>
                      <template #suffix>
                        <n-icon><EnterOutlined/></n-icon>
                      </template>
                    </n-input>
                  </div>
                  <BasicTable
                    :columns="userColumns"
                    :request="loadDataTable"
                    :row-key="(row) => row.id"
                    ref="actionRef"
                    id="moss-basicTable-ccToUser"
                    :bordered="false"
                    :actionColumn="actionColumnCcToUser"
                    :max-height="200"
                    :showTopOps="false"
                  >
                    <template #action>
                      <TableAction />
                    </template>
                  </BasicTable>
                </div>
              </template>
            </n-select>
          </n-form-item>
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="任务节点结束时间"
            path="nodeTaskEndTime"
          >
            <n-date-picker
              style="width: 100%"
              v-model:value="setNodeData.nodeTaskEndTime"
              value-format="yyyy.MM.dd HH:mm:ss"
              type="datetime"
            />
          </n-form-item>
          <n-form-item
            v-if="
              setNodeData.type === NodeType.TASK || setNodeData.type === NodeType.MERGE
            "
            label="达到任务节点结束时间是否自动通过"
            path="reachEndTimePass"
          >
            <n-switch v-model:value="setNodeData.reachEndTimePass" />
          </n-form-item>
        </n-form>
        <template #footer>
          <n-space>
            <n-button @click="handleCloseDrawer"> 取消 </n-button>
            <n-button type="primary" @click="handleSaveNode"> 确定 </n-button>
          </n-space>
        </template>
      </n-drawer-content>
    </n-drawer>

    <n-modal
      v-model:show="showModal"
      :show-icon="false"
      preset="dialog"
      title="设置任务分解次数"
    >
      <n-input-number v-model:value="taskNum" clearable />
      <div style="justify-content: end; display: flex; margin-top: 15px">
        <n-space>
          <n-button ghost icon-placement="left" @click="handleCloseModel">
            取消
          </n-button>
          <n-button type="primary" @click="confirmFormTaskNum">提交</n-button>
        </n-space>
      </div>
    </n-modal>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, reactive,h } from 'vue';
import { Node, Graph, StringExt } from '@antv/x6';
import { NDropdown, NTooltip,NIcon, type FormRules } from 'naive-ui';
import { userByIds,usersPages } from '@/api/system/user';
import { PlusOutlined,EnterOutlined,SearchOutlined } from "@vicons/antd";
import { userColumns } from "./userColumns";
import { BasicTable, TableAction } from "@/components/Table";
// 加工类型列表
const PROCESSING_TYPE_LIST = [
    {
        key: 'TASK',
        label: '流程任务',
    },
    {
        key: 'CONDITION',
        label: '流程条件',
    },
    {
        key: 'MERGE',
        label: '任务合并',
    },
    {
        key: 'END',
        label: '流程结束',
    },
    {
        key: 'DECOMPOSE',
        label: '任务分解',
    },
]
const conditionOptions = [
    { label: '等于', value: '=' }, { label: '大于', value: '>' }, { label: '小于', value: '<' },
    { label: '大于等于', value: '>=' }, { label: '小于等于', value: '<=' }, { label: '不等于', value: '!=' },
]
const rules: FormRules = {
    name: {
        required: true,
        trigger: ['blur', 'input'],
        message: '请输入节点名称',
    },
    headUser: {
        type: 'array',
        required: true,
        trigger: ['blur', 'select'],
        message: '请输入负责人',
    }
};

let nodeMergeId: string = 'node-merge';
export default defineComponent({
    components: {
        NDropdown,
        NTooltip,
        NIcon,
        BasicTable,
        TableAction,
        EnterOutlined,
        SearchOutlined,
        PlusOutlined,
    },
    props: {
        node: {
            type: Node,
            default: null,
        },
    },
    setup(props) {
        const node = props.node;
        const plusActionSelected = ref<boolean>(false);
        const data = node?.getData();
        const { name, type, status } = data;
        const drawerShow = ref<boolean>(false);
        const drawerTitle = ref<string>('');
        const showModal = ref<boolean>(false);
        const taskNum = ref<number>(0);
        const $message = window['$message'];
        const selectLoading = ref<boolean>(false);
        const usersOptions = ref<any[]>([]);
        const headUsers = ref<any[]>([]);
        const ccToUsers = ref<any[]>([]);
        const actionRef = ref();
        const defaultValueRef = () => ({
            // 节点名称
            name: '',
            // 节点类型
            type: null,
            // 负责人
            headUser: <any>[],
            // 抄送人
            ccToUser: <any>[],
            // 节点任务结束时间
            nodeTaskEndTime: null,
            // 达到节点任务时间是否自动通过
            reachEndTimePass: false,
            sign: 'OR',
            condition: {
                // 条件字段
                field: '',
                // 条件值
                conditionValue: '',
                // 条件表达式
                term: '',
                // 字段描述
                desc: ''
            }
        });
        const searchName = ref<string>('');
        const loadDataTable = async (res: any) => {
            return await usersPages({ ...{name:searchName.value}, ...res,...{showTenant:true} });
        };

        const actionColumn = reactive({
            title: "操作",
            key: "action",
            fixed: "right",
            render(record) {
                return h(TableAction as any, {
                    style: "text",
                    actions: createActions(record),
                });
            },
        });
        function createActions(record) {
            return [
                {
                label: "选择",
                type: "primary",
                icon: PlusOutlined,
                onClick: onCheckedRow.bind(null, record),
                ifShow: () => {
                    return true;
                },
                },
            ];
        }
        const actionColumnCcToUser = reactive({
            title: "操作",
            key: "action",
            fixed: "right",
            render(record) {
                return h(TableAction as any, {
                    style: "text",
                    actions: createActionsCcToUser(record),
                });
            },
        });
        function createActionsCcToUser(record) {
            return [
                {
                label: "选择",
                type: "primary",
                icon: PlusOutlined,
                onClick: onCheckedRowCcToUser.bind(null, record),
                ifShow: () => {
                    return true;
                },
                },
            ];
        }
        function onCheckedRowCcToUser(record: Recordable) {
            setNodeData.ccToUser.push(record.id);
            ccToUsers.value.push({ label: record.name, value: record.id });
        }
        function onCheckedRow(record: Recordable) {
            setNodeData.headUser.push(record.id)
            headUsers.value.push({ label: record.name, value: record.id });
        }
        function reloadTable() {
            actionRef.value.reload();
        }
        const handleChange =(v: string)=> {
          searchName.value = v;
          reloadTable();           
        }
        let setNodeData = reactive(defaultValueRef());
        enum NodeType {
            START = 'START', // 开始
            TASK = 'TASK', // 任务
            CONDITION = 'CONDITION', // 条件
            MERGE = 'MERGE', // 合并
            END = 'END', // 结束
            DECOMPOSE = 'DECOMPOSE', // 分解
        }
        enum CellStatus {
            DEFAULT = 'default',
            SUCCESS = 'success',
            ERROR = 'error',
        }
        // 节点位置信息
        interface Position {
            x: number
            y: number
        }
        // 不同节点类型的icon
        const NODE_TYPE_LOGO = {
            START: 'https://qxht.xiaomy.net/file/icon/flow/flowStart.png', // 开始
            TASK: 'https://qxht.xiaomy.net/file/icon/flow/flowTask.png', // 任务
            CONDITION: 'https://qxht.xiaomy.net/file/icon/flow/flowTj.png', // 条件
            MERGE: 'https://qxht.xiaomy.net/file/icon/flow/flowHb.png', // 合并
            END: 'https://qxht.xiaomy.net/file/icon/flow/flowEnd.png', // 结束
        }

        onMounted(() => { });
        // 创建下游的节点和边
        const createDownstream = (type: NodeType) => {
            const { graph } = node.model || {}
            console.log('graph', graph);
            if (graph) {
                // 获取下游节点的初始位置信息
                const position = getDownstreamNodePosition(node, graph)
                console.log('position', position);
                // 创建下游节点
                const newNode = createNode(type, graph, position)
                console.log('newNode', newNode);
                const source = node.id
                const target = newNode.id
                // 创建该节点出发到下游节点的边
                createEdge(source, target, graph)
            }
        }
        /**
         * 根据起点初始下游节点的位置信息
         * @param node 起始节点
         * @param graph
         * @returns
         */
        const getDownstreamNodePosition = (
            node: Node,
            graph: Graph,
            dx = 250,
            dy = 100,
        ) => {
            // 找出画布中以该起始节点为起点的相关边的终点id集合
            const downstreamNodeIdList: string[] = []
            graph.getEdges().forEach((edge) => {
                const originEdge = edge.toJSON()?.data
                if (originEdge.source === node.id) {
                    downstreamNodeIdList.push(originEdge.target)
                }
            })
            // 获取起点的位置信息
            const position = node.getPosition()
            let minX = Infinity
            let maxY = -Infinity
            graph.getNodes().forEach((graphNode) => {
                if (downstreamNodeIdList.indexOf(graphNode.id) > -1) {
                    const nodePosition = graphNode.getPosition()
                    // 找到所有节点中最左侧的节点的x坐标
                    if (nodePosition.x < minX) {
                        minX = nodePosition.x
                    }
                    // 找到所有节点中最x下方的节点的y坐标
                    if (nodePosition.y > maxY) {
                        maxY = nodePosition.y
                    }
                }
            })

            return {
                x: minX !== Infinity ? minX : position.x + dx,
                y: maxY !== -Infinity ? maxY + dy : position.y,
            }
        }
        /**
         * 创建节点并添加到画布
         * @param type 节点类型
         * @param graph
         * @param position 节点位置
         * @returns
         */
        const createNode = (
            type: NodeType,
            graph: Graph,
            position?: Position
        ) => {
            if (!graph) {
                return {}
            }
            let newNode = {}
            const sameTypeNodes = graph
                .getNodes()
                .filter((item) => item.getData()?.key === type)
            const typeName = PROCESSING_TYPE_LIST?.find(
                (item) => item.key === type,
            )?.label
            const id = type === NodeType.END ? 'node-end' : (type === NodeType.MERGE ? nodeMergeId : StringExt.uuid())
            const node = {
                id,
                shape: 'data-processing-dag-node',
                x: position?.x,
                y: position?.y,
                ports: getPortsByType(type, id),
                data: {
                    name: type === NodeType.END ? '结束' : `${typeName}_${sameTypeNodes.length + 1}`,
                    type,
                },
                tools: [
                    {
                        name: 'button-remove',
                        args: { x: 200, y: 10 },
                    },
                ],
            }
            newNode = graph.addNode(node)
            return newNode
        }
        // 根据节点的类型获取ports
        const getPortsByType = (type: NodeType, nodeId: string) => {
            let ports: any = []
            switch (type) {
                case NodeType.START:
                    ports = [
                        {
                            id: `${nodeId}-out`,
                            group: 'out',
                        },
                    ]
                    break
                case NodeType.END:
                    ports = [
                        {
                            id: `${nodeId}-in`,
                            group: 'in',
                        },
                    ]
                    break
                default:
                    ports = [
                        {
                            id: `${nodeId}-in`,
                            group: 'in',
                        },
                        {
                            id: `${nodeId}-out`,
                            group: 'out',
                        },
                    ]
                    break
            }
            return ports
        }
        /**
         * 创建边并添加到画布
         * @param source
         * @param target
         * @param graph
         */
        const createEdge = (source: string, target: string, graph: Graph) => {
            const edge = {
                id: StringExt.uuid(),
                shape: 'data-processing-curve',
                source: {
                    cell: source,
                    port: `${source}-out`,
                },
                target: {
                    cell: target,
                    port: `${target}-in`,
                },
                zIndex: -1,
                data: {
                    source,
                    target,
                },
            }
            if (graph) {
                graph.addEdge(edge)
            }
        }
        // 点击添加下游+号
        const clickPlusDragMenu = (type: NodeType, superiorType: NodeType) => {
            if (type === NodeType.END) {
                nodeMergeId = StringExt.uuid();
            }
            if (superiorType === NodeType.START && type === NodeType.DECOMPOSE) {
                $message.error('开始节点不能分解任务');
                return;
            }
            if (superiorType === NodeType.START && type === NodeType.MERGE) {
                $message.error('开始节点不能合并任务');
                return;
            }
            if (superiorType === NodeType.CONDITION && type === NodeType.MERGE) {
                $message.error('条件节点不能合并任务');
                return;
            }
            if (superiorType === NodeType.CONDITION && type === NodeType.DECOMPOSE) {
                $message.error('条件节点不能分解任务');
                return;
            }
            if (type === NodeType.DECOMPOSE) {
                nodeMergeId = StringExt.uuid();
                showModal.value = true;
            } else {
                createDownstream(type)
            }
            plusActionSelected.value = false;
        }
        // 确定添加下游任务数量
        const confirmFormTaskNum = () => {
            for (let i = 0; i < taskNum.value; i++) {
                createDownstream(NodeType.TASK)
            }
            showModal.value = false;
        }
        // 添加下游菜单的打开状态变化
        const onPlusDropdownOpenChange = (value: boolean) => {
            plusActionSelected.value = value;
        }

        // 鼠标进入矩形主区域的时候显示连接桩
        const onMainMouseEnter = () => {
            // 获取该节点下的所有连接桩
            const ports = node.getPorts() || []
            ports.forEach((port) => {
                node.setPortProp(port.id, 'attrs/circle', {
                    fill: '#fff',
                    stroke: '#85A5FF',
                })
            })
        }

        // 鼠标离开矩形主区域的时候隐藏连接桩
        const onMainMouseLeave = () => {
            // 获取该节点下的所有连接桩
            const ports = node.getPorts() || []
            ports.forEach((port) => {
                node.setPortProp(port.id, 'attrs/circle', {
                    fill: 'transparent',
                    stroke: 'transparent',
                })
            })
        }
        // 点击节点
        const onClickNode = async () => {
            // 获取该节点下的数据
            const data = node.getData() || [];
            console.log('data', data);
            drawerTitle.value = data.name;
            drawerShow.value = (data.type === NodeType.START || data.type === NodeType.END) ? false : true;
            setNodeData = Object.assign(setNodeData, data);
            const users = await userByIds({ userIds: data.headUser });
            const ccToUserList = await userByIds({ userIds: data.ccToUser });
            headUsers.value = users.map(item => ({ label: item.name, value: item.id }));
            ccToUsers.value = ccToUserList.map(item => ({ label: item.name, value: item.id }));
        }
        // 关闭节点数据设置
        const handleCloseDrawer = () => {
            drawerShow.value = false;
        }
        // 保存节点数据
        const handleSaveNode = () => {
            //导出整个设计器的节点数据以及节点关系
            const { graph } = node.model || {};
            let data = node.getData() || [];
            if (data.type === NodeType.CONDITION) {
                data.name = setNodeData.condition.desc + setNodeData.condition.term + setNodeData.condition.conditionValue;
                data.condition = setNodeData.condition;
            } else {
                data = Object.assign({}, setNodeData);
            }
            node.setData({ ...data })
            // 更新整个画布的数据
            graph?.fromJSON(graph?.toJSON())
            handleCloseDrawer();
        }
        // 关闭设置任务分解次数框
        const handleCloseModel = () => {
            showModal.value = false;
        }
        return {
            PROCESSING_TYPE_LIST,
            onPlusDropdownOpenChange,
            NodeType,
            type,
            CellStatus,
            status,
            name,
            onMainMouseEnter,
            onMainMouseLeave,
            NODE_TYPE_LOGO,
            plusActionSelected,
            clickPlusDragMenu,
            onClickNode,
            drawerShow,
            handleCloseDrawer,
            drawerTitle,
            handleSaveNode,
            setNodeData,
            rules,
            showModal,
            taskNum,
            handleCloseModel,
            confirmFormTaskNum,
            conditionOptions,
            usersOptions,
            selectLoading,
            headUsers,
            actionRef,
            handleChange,
            loadDataTable,
            actionColumn,
            onCheckedRow,
            userColumns,
            ccToUsers,
            actionColumnCcToUser
        }
    },

})
</script>
<style scoped>
.data-processing-dag-node {
  display: flex;
  flex-direction: row;
  align-items: center;
}

/* .main-info{
    margin-top: 10px;
} */

.main-area {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 12px;
  width: 210px;
  height: 38px;
  color: rgba(0, 0, 0, 65%);
  font-size: 12px;
  font-family: PingFangSC;
  line-height: 12px;
  background-color: #fff;
  box-shadow: 0 -1px 4px 0 rgba(209, 209, 209, 50%),
    1px 1px 4px 0 rgba(217, 217, 217, 50%);
  border-radius: 2px;
  border: 1px solid transparent;
}

.main-area:hover {
  border: 1px solid rgba(0, 0, 0, 10%);
  box-shadow: 0 -2px 4px 0 rgba(209, 209, 209, 50%),
    2px 2px 4px 0 rgba(217, 217, 217, 50%);
}

.node-logo {
  display: inline-block;
  width: 24px;
  height: 24px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100%;
  margin-top: -6px;
}

.node-name {
  overflow: hidden;
  display: inline-block;
  width: 80px;
  margin-left: 6px;
  color: rgba(0, 0, 0, 65%);
  font-size: 12px;
  font-family: PingFangSC;
  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.status-action {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.status-icon {
  display: inline-block;
  width: 24px;
  height: 24px;
}

.status-icon-error {
  background: url("https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ")
    no-repeat center center / 100% 100%;
}

.status-icon-success {
  background: url("https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ")
    no-repeat center center / 100% 100%;
}

.more-action-container {
  margin-left: 12px;
  width: 15px;
  height: 15px;
  text-align: center;
  cursor: pointer;
}

.more-action {
  display: inline-block;
  width: 3px;
  height: 15px;
  background: url("https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*tFw7SIy-ttQAAAAAAAAAAAAADtOHAQ/original")
    no-repeat center center / 100% 100%;
}

.plus-dag {
  visibility: hidden;
  position: relative;
  margin-left: 12px;
  height: 48px;
}

.plus-action {
  position: absolute;
  top: calc(50% - 8px);
  left: 0;
  width: 16px;
  height: 16px;
  background: url("https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*ScX2R4ODfokAAAAAAAAAAAAADtOHAQ/original")
    no-repeat center center / 100% 100%;
  cursor: pointer;
}

.plus-action:hover {
  background-image: url("https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*tRaoS5XhsuQAAAAAAAAAAAAADtOHAQ/original");
}

.plus-action:active,
.plus-action-selected {
  background-image: url("https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*k9cnSaSmlw4AAAAAAAAAAAAADtOHAQ/original");
}

.x6-node-selected .main-area {
  border-color: #3471f9;
}

.x6-node-selected .plus-dag {
  visibility: visible;
}

.processing-node-menu {
  padding: 2px 0;
  width: 105px;
  background-color: #fff;
  box-shadow: 0 9px 28px 8px rgba(0, 0, 0, 5%), 0 6px 16px 0 rgba(0, 0, 0, 8%),
    0 3px 6px -4px rgba(0, 0, 0, 12%);
  border-radius: 2px;
}

.processing-node-menu ul {
  margin: 0;
  padding: 0;
}

.processing-node-menu li {
  list-style: none;
}

.each-sub-menu {
  padding: 6px 12px;
  width: 100%;
}

.each-sub-menu:hover {
  background-color: rgba(0, 0, 0, 4%);
}

.each-sub-menu a {
  display: inline-block;
  width: 100%;
  height: 16px;
  font-family: PingFangSC;
  font-weight: 400;
  font-size: 12px;
  color: rgba(0, 0, 0, 65%);
}

.each-sub-menu span {
  margin-left: 8px;
  vertical-align: top;
}

.each-disabled-sub-menu a {
  cursor: not-allowed;
  color: rgba(0, 0, 0, 35%);
}

.node-mini-logo {
  display: inline-block;
  width: 16px;
  height: 16px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100%;
  vertical-align: top;
}

@keyframes running-line {
  to {
    stroke-dashoffset: -1000;
  }
}

.plus-dag-dropdown {
  width: 120px;
  background-color: #fff;
  border-radius: 6px;
  z-index: 999999;
  box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
    0 9px 28px 8px rgba(0, 0, 0, 0.05);
}
</style>
